//////////////////////////////////////////////////////////////////////
//!\file FlacMetaDataManager.cpp
//!\brief Reads out Metadata from FLAC files.
//!\author timmsusa@googlemail.com
//////////////////////////////////////////////////////////////////////

#include "FlacMetaDataManager.hpp"
#include <QDebug>
#include <QCryptographicHash>

#include <string>
#include <iostream>

FlacMetadataManager::FlacMetadataManager():
    m_fileHdrLabel( "File" ),
    m_md5HdrLabel( "Audio MD5" ),
    m_chansHdrLabel( "Channels" ),
    m_bpsHdrLabel( "Bits per sample" ),
    m_srateHdrLabel( "Samplerate" ),
    m_numSamplesHdrLabel( "Total samples" ),
    m_minBSizeHdrLabel( "Min. blocksize" ),
    m_maxBSizeHdrLabel( "Max. blocksize" ),
    m_minFSizeHdrLabel( "Min. framesize" ),
    m_maxFSizeHdrLabel( "Max. framesize" ),
    m_encoderHdrLabel( "Encoder" ),
    m_invalidDataHdrLabel( "Has invalid metadata" ),
    m_picHdrLabel( "Has picture" ),
    m_cueHdrLabel( "Has CUE sheet" ),
    m_commentHdrLabel( "Vorbis Comments" ),
    m_numSeekpointsHdrLabel( "Number of seekpoints" ),
    // 	m_seekpointsHdrLabel( "Seekpoints ( sample number/stream offset/frame samples)" )
    m_seekpointsHdrLabel( "Seekpoints" )
{
    initMetadataKeys();
}

const QHash< QString, int >& FlacMetadataManager::getMetaLut()
{
    return m_lut;
}

void FlacMetadataManager::initMetadataKeys()
{
    qDebug() << "FlacMetadataManager::initMetadataKeys";

    // the following is supposed to emulate an ordered hash / a list with string index
    // there might be better ways to do this
    m_metadataKeys
            << m_fileHdrLabel
            << m_md5HdrLabel
            << m_chansHdrLabel
            << m_bpsHdrLabel
            << m_srateHdrLabel
            << m_numSamplesHdrLabel
            << m_minBSizeHdrLabel
            << m_maxBSizeHdrLabel
            << m_minFSizeHdrLabel
            << m_maxFSizeHdrLabel
            << m_encoderHdrLabel
            << m_invalidDataHdrLabel
            << m_picHdrLabel
            << m_cueHdrLabel
            << m_commentHdrLabel
            << m_numSeekpointsHdrLabel
            << m_seekpointsHdrLabel;

    m_lut[ m_fileHdrLabel ] = m_metadataKeys.indexOf( m_fileHdrLabel );
    m_lut[ m_md5HdrLabel ] = m_metadataKeys.indexOf( m_md5HdrLabel );
    m_lut[ m_chansHdrLabel ] = m_metadataKeys.indexOf( m_chansHdrLabel );
    m_lut[ m_bpsHdrLabel ] = m_metadataKeys.indexOf( m_bpsHdrLabel );
    m_lut[ m_srateHdrLabel ] = m_metadataKeys.indexOf( m_srateHdrLabel );
    m_lut[ m_numSamplesHdrLabel ] = m_metadataKeys.indexOf( m_numSamplesHdrLabel );
    m_lut[ m_minBSizeHdrLabel ] = m_metadataKeys.indexOf( m_minBSizeHdrLabel );
    m_lut[ m_maxBSizeHdrLabel ] = m_metadataKeys.indexOf( m_maxBSizeHdrLabel );
    m_lut[ m_minFSizeHdrLabel ] = m_metadataKeys.indexOf( m_minFSizeHdrLabel );
    m_lut[ m_maxFSizeHdrLabel ] = m_metadataKeys.indexOf( m_maxFSizeHdrLabel );
    m_lut[ m_encoderHdrLabel ] = m_metadataKeys.indexOf( m_encoderHdrLabel );
    m_lut[ m_invalidDataHdrLabel ] = m_metadataKeys.indexOf( m_invalidDataHdrLabel );
    m_lut[ m_picHdrLabel ] = m_metadataKeys.indexOf( m_picHdrLabel );
    m_lut[ m_cueHdrLabel ] = m_metadataKeys.indexOf( m_cueHdrLabel );
    m_lut[ m_commentHdrLabel ] = m_metadataKeys.indexOf( m_commentHdrLabel );
    m_lut[ m_numSeekpointsHdrLabel ] = m_metadataKeys.indexOf( m_numSeekpointsHdrLabel );
    m_lut[ m_seekpointsHdrLabel ] = m_metadataKeys.indexOf( m_seekpointsHdrLabel );

}

bool FlacMetadataManager::isFlacValid(const QString& filepath)
{
    //    qDebug() << "FlacMetadataManager::isFlacValid";

    bool ret = false;
    FLAC::Metadata::SimpleIterator iter;

    // Proof iterator
    if( ! iter.is_valid() )
    {
        ret = false;
    }
    else
    {

        // Initialize with 'filepath'
        //if( ! iter.init( filepath.toLocal8Bit(), true, true ) )
        if( ! iter.init( QFile::encodeName(filepath).data(), true, true ) )
        {
            ret=false;
            //qDebug() << "Cannot open Flac: " << QFile::encodeName(filepath).data();
        }
        else
        {
            ret=true;
        }
    }
    return ret;
}

QString FlacMetadataManager::extractMd5(const QString &pathToTrack)
{
    QString retVal = "";

    // Create FLAC metadata iterator to extract a block per cycle
    FLAC::Metadata::SimpleIterator iter;

    // Proof iterator
    if( ! iter.is_valid() )
    {
        onError( iter.status().as_cstring() );
        retVal="";
    }
    else
    {
        // Initialize iterator
        if( (!iter.init( QFile::encodeName(pathToTrack), true, true )))// && (iter.status() != ::FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE) )
        {
            onError( iter.status().as_cstring() );
            retVal="";
        }
        else
        {
            ::FLAC__MetadataType blockType = iter.get_block_type();
            FLAC::Metadata::Prototype *block = iter.get_block();

            if( ! block )
            {
                onError( iter.status().as_cstring() );
                retVal="";
            }
            else
            {
                // Extract FLAC stream-info from block
                if ( blockType == ::FLAC__METADATA_TYPE_STREAMINFO )
                {
                    FLAC::Metadata::StreamInfo *streaminfo =
                            dynamic_cast< FLAC::Metadata::StreamInfo * >( block );

                    // Extract md5sum
                    const FLAC__byte *md5sum = streaminfo->get_md5sum();

                    // Format md5 to textstream
                    QString md5sumString;
                    QTextStream textStream( &md5sumString );

                    textStream
                            << hex
                            << qSetPadChar( '0' )
                            << qSetFieldWidth( 2 );

                    for( size_t i = 0; i < 16; i++ )
                    {
                        textStream << static_cast< unsigned short >( md5sum[i] );  // do me better
                    }
                    retVal = md5sumString;
                }
            }

            // clean up
            if (block)
                delete block;
            else
                qDebug() << "Block invalid!\n";
        }
    }
    return retVal;
}



QVector< QString > FlacMetadataManager::getMetaDataByFilePath(const QString& filePath)
{
    //qDebug() << "FlacMetadataManager::getMetaDataByFilePath";
    QVector< QString > metadataValues( m_metadataKeys.size(), "" );

    QString lineEnding( "\n" );

    //qDebug() << "-----";

    metadataValues[ m_lut[ m_cueHdrLabel ] ] = QString::fromUtf8( "no" );
    metadataValues[ m_lut[ m_invalidDataHdrLabel ] ] = QString::fromUtf8( "no" );
    metadataValues[ m_lut[ m_picHdrLabel ] ] = QString::fromUtf8( "no" );

    // Strip out basename
    QString name = Utils::getBaseFromPath(filePath);
    metadataValues[ m_lut[ m_fileHdrLabel ] ] = name;

    // Begin of FLAC code
    // Create FLAC metadata iterator to extract a block per cycle
    FLAC::Metadata::SimpleIterator iter;

    // Proof iterator
    if( ! iter.is_valid() )
    {
        onError( iter.status().as_cstring() );
        return metadataValues;
    }

    // Initialize with 'filepath'

    if( ! iter.init( QFile::encodeName(filePath), true, true ) )
    {
        // TODO: Check, why kyrillic is not working for filepaths!
        //::std::string str = ::std::string(iter.status().as_cstring()) + " " + filePath.toStdString();
        //onError( str.c_str() );
        return metadataValues;
    }

    // Iterate and extract blockwise
    while( ! iter.is_last() )
    {
        ::FLAC__MetadataType blockType = iter.get_block_type();
        FLAC::Metadata::Prototype *block = iter.get_block();

        if( ! block )
        {
            onError( iter.status().as_cstring() );
        }

        // Extract FLAC stream-info from block
        if( blockType == ::FLAC__METADATA_TYPE_STREAMINFO )
        {
            //      qDebug() << "FLAC__METADATA_TYPE_STREAMINFO" << "\n";

            FLAC::Metadata::StreamInfo *streaminfo =
                    dynamic_cast< FLAC::Metadata::StreamInfo * >( block );

            // Extract md5sum
            const FLAC__byte *md5sum = streaminfo->get_md5sum();

            // Format md5 to textstream
            QString md5sumString;
            QTextStream textStream( &md5sumString );

            textStream
                    << hex
                    << qSetPadChar( '0' )
                    << qSetFieldWidth( 2 );

            for( int i = 0; i < 16; i++ )
            {
                textStream << static_cast< unsigned short >( md5sum[i] );  // do me better
            }

            // Set stream-info to local members
            metadataValues[ m_lut[ m_md5HdrLabel ] ] = md5sumString;
            metadataValues[ m_lut[ m_chansHdrLabel ] ] = QString::number( streaminfo->get_channels() );
            metadataValues[ m_lut[ m_bpsHdrLabel ] ] = QString::number( streaminfo->get_bits_per_sample() );
            metadataValues[ m_lut[ m_srateHdrLabel ] ] = QString::number( streaminfo->get_sample_rate() );
            metadataValues[ m_lut[ m_numSamplesHdrLabel ] ] = QString::number( streaminfo->get_total_samples() );
            metadataValues[ m_lut[ m_minBSizeHdrLabel ] ] = QString::number( streaminfo->get_min_blocksize() );
            metadataValues[ m_lut[ m_maxBSizeHdrLabel ] ] = QString::number( streaminfo->get_max_blocksize() );
            metadataValues[ m_lut[ m_minFSizeHdrLabel ] ] = QString::number( streaminfo->get_min_framesize() );
            metadataValues[ m_lut[ m_maxFSizeHdrLabel ] ] = QString::number( streaminfo->get_max_framesize() );
            /*
            // Print out val
            qDebug()
                    << "\t" << m_md5HdrLabel << ":" << metadataValues[ m_lut[ m_md5HdrLabel ] ] << "\n"
                    << "\t" << m_chansHdrLabel << ":" << metadataValues[ m_lut[ m_chansHdrLabel ] ] << "\n"
                    << "\t" << m_bpsHdrLabel << ":" << metadataValues[ m_lut[ m_bpsHdrLabel ] ] << "\n"
                    << "\t" << m_srateHdrLabel << ":" << metadataValues[ m_lut[ m_srateHdrLabel ] ] << "\n"
                    << "\t" << m_numSamplesHdrLabel << ":" << metadataValues[ m_lut[ m_numSamplesHdrLabel ] ] << "\n"
                    << "\t" << m_minBSizeHdrLabel << ":" << metadataValues[ m_lut[ m_minBSizeHdrLabel ] ] << "\n"
                    << "\t" << m_maxBSizeHdrLabel << ":" << metadataValues[ m_lut[ m_maxBSizeHdrLabel ] ] << "\n"
                    << "\t" << m_minFSizeHdrLabel << ":" << metadataValues[ m_lut[ m_minFSizeHdrLabel ] ] << "\n"
                    << "\t" << m_maxFSizeHdrLabel << ":" << metadataValues[ m_lut[ m_maxFSizeHdrLabel ] ] << "\n"
                    << "\n";
                    */
        }
        else if( blockType == ::FLAC__METADATA_TYPE_PADDING )
        {
            // qDebug() << "FLAC__METADATA_TYPE_PADDING" << "\n";
        }
        else if( blockType == ::FLAC__METADATA_TYPE_APPLICATION )
        {
            FLAC::Metadata::Application *app =
                    dynamic_cast< FLAC::Metadata::Application * >( block );

            QString appId = QString::fromLocal8Bit( reinterpret_cast< const char * >( app->get_id() ), sizeof( char ) * 4 );
            QString appData = QString::fromLocal8Bit( reinterpret_cast< const char * >( app->get_data() ) );
            /*
            qDebug()
                    << "FLAC__METADATA_TYPE_APPLICATION: "
                    << appId
                    << " "
                    << "("
                    << appData
                    << ")"
                    << "\n";
*/
            metadataValues[ m_lut[ m_encoderHdrLabel ] ].clear();

            QTextStream textStream( &metadataValues[ m_lut[ m_encoderHdrLabel ] ] );

            textStream
                    << appId
                    << " "
                    << "("
                    << appData
                    << ")";
        }
        else if( blockType == ::FLAC__METADATA_TYPE_SEEKTABLE )
        {
            //  qDebug() << "FLAC__METADATA_TYPE_SEEKTABLE" << "\n";

            FLAC::Metadata::SeekTable *seekTable =
                    dynamic_cast< FLAC::Metadata::SeekTable * >( block );

            unsigned int numPoints = seekTable->get_num_points();

            //  qDebug() << numPoints << " seekpoints: ";

            metadataValues[ m_lut[ m_numSeekpointsHdrLabel ] ] = QString::number( numPoints );

            metadataValues[ m_lut[ m_seekpointsHdrLabel ] ].clear();

            for( unsigned int i = 0; i < numPoints; i++ )
            {
                FLAC__StreamMetadata_SeekPoint seekPoint = seekTable->get_point( i );
                /*
                qDebug()
                        << "\tsample_number: " << seekPoint.sample_number
                        << "\tstream_offset: " << seekPoint.stream_offset
                        << "\tframe_samples: " << seekPoint.frame_samples;
*/
                QTextStream textStream( &metadataValues[ m_lut[ m_seekpointsHdrLabel ] ] );

                textStream
                        << QString::number( seekPoint.sample_number )
                        << QString::fromUtf8( "/" )
                        << QString::number( seekPoint.stream_offset )
                        << QString::fromUtf8( "/" )
                        << QString::number( seekPoint.frame_samples )
                        << lineEnding;
            }
            /*
            qDebug()
                    << "\n";
*/
            metadataValues[ m_lut[ m_seekpointsHdrLabel ] ].chop( 1 );
        }
        else if( blockType == ::FLAC__METADATA_TYPE_VORBIS_COMMENT )
        {
            // qDebug() << "FLAC__METADATA_TYPE_VORBIS_COMMENT";

            FLAC::Metadata::VorbisComment *tags =
                    dynamic_cast< FLAC::Metadata::VorbisComment * >( block );

            QString vorbisComments;

            for( unsigned int i = 0; i < tags->get_num_comments(); i++ )
            {
                FLAC::Metadata::VorbisComment::Entry entry = tags->get_comment( i );

                QTextStream textStream ( &vorbisComments );

                textStream
                        << QString::fromUtf8( entry.get_field_name() )
                        << QString::fromUtf8( " : \"" )
                        << QString::fromUtf8( entry.get_field_value() )
                        << "\""
                        << lineEnding;
                /*
                qDebug()
                        << "\t"
                        << QString::fromUtf8( entry.get_field_name() )
                        << QString::fromUtf8( " : " )
                        << QString::fromUtf8( entry.get_field_value() )
                        << "";
                        */
            }

            vorbisComments.chop( 1 );

            metadataValues[ m_lut[ m_commentHdrLabel ] ] = vorbisComments;
        }
        else if( blockType == ::FLAC__METADATA_TYPE_CUESHEET )
        {
            // qDebug() << "FLAC__METADATA_TYPE_CUESHEET" << "\n";

            metadataValues[ m_lut[ m_cueHdrLabel ] ] = QString::fromUtf8( "yes" );
        }
        else if( blockType == ::FLAC__METADATA_TYPE_PICTURE )
        {
            // qDebug() << "FLAC__METADATA_TYPE_PICTURE" << "\n";

            metadataValues[ m_lut[ m_picHdrLabel ] ] = QString::fromUtf8( "yes" );
        }
        else if( blockType == ::FLAC__METADATA_TYPE_UNDEFINED )
        {
            //  qDebug() << "FLAC__METADATA_TYPE_UNDEFINED" << "\n";

            metadataValues[ m_lut[ m_invalidDataHdrLabel ] ] = QString::fromUtf8( "yes" );
        }

        delete block;

        if( ! iter.next() )
        {
            onError( iter.status().as_cstring() );
        }
    }// End of filling metadataValues


    return metadataValues;
}

QString FlacMetadataManager::getTrackMd5ByPath(const QString &filePath)
{
    QString metaTmp;
    vecToStr(getMetaDataByFilePath(filePath), metaTmp);
    QByteArray val = QCryptographicHash::hash((metaTmp.toUtf8()),QCryptographicHash::Md5).toHex();
    //qDebug() << "FlacMetadataManager::getTrackMd5ByPath: " << filePath << " -> HASH: " << val.toHex() << " " << QString(val);
    return QString(val);
}

/*
void FlacMetadataManager::fileInfoToMetaData(QVector<QString> &metadataValues, const QString& filePath)
{
    qDebug() << "FlacMetadataManager::fileInfoToMetaData";

    QString lineEnding( "" );

    if( isFile )
    {
        lineEnding = "\n";
    }
    else
    {
        lineEnding = " ";
    }

    metadataValues = getMetaDataByFilePath(filePath);
}
*/
